第二章 v4l2 framework简介

本章开始介绍V4L2的框架。首先看一下我自己画的v4l2框架图。接下来的章节我们会逐个分析图片中的每个结构体。

_images/v4l2-framework.png

V4L2 简易框图

2.1 video_device

video_device的首要任务就是在/dev/目录下创建设备文件。V4L2 简易框图中可以看出,video_device处于最上端。由于图片太大,将video_device相关的图片截图如下:

video_device截图

_images/port-vdev.png

video_device截图

下面是video_device的定义:

struct video_device
{
#if defined(CONFIG_MEDIA_CONTROLLER)
struct media_entity entity;
#endif
/* device ops */
const struct v4l2_file_operations *fops;

/* sysfs */
struct device dev;          /* v4l device */
struct cdev *cdev;          /* character device */

/* Set either parent or v4l2_dev if your driver uses v4l2_device */
struct device *parent;              /* device parent */
struct v4l2_device *v4l2_dev;       /* v4l2_device parent */

/* Control handler associated with this device node. May be NULL. */
struct v4l2_ctrl_handler *ctrl_handler;

/* Priority state. If NULL, then v4l2_dev->prio will be used. */
struct v4l2_prio_state *prio;

/* device info */
char name[32];
int vfl_type;
/* 'minor' is set to -1 if the registration failed */
int minor;
u16 num;
/* use bitops to set/clear/test flags */
unsigned long flags;
/* attribute to differentiate multiple indices on one physical device */
int index;

/* V4L2 file handles */
spinlock_t          fh_lock; /* Lock for all v4l2_fhs */
struct list_head    fh_list; /* List of struct v4l2_fh */

int debug;                  /* Activates debug level*/

/* Video standard vars */
v4l2_std_id tvnorms;                /* Supported tv norms */
v4l2_std_id current_norm;   /* Current tvnorm */

/* callbacks */
void (*release)(struct video_device *vdev);

/* ioctl callbacks */
const struct v4l2_ioctl_ops *ioctl_ops;

/* serialization lock */
struct mutex *lock;
};

2.1.1 创建及销毁

video_device可以动态分配,或者静态分配(嵌入到其他结构体中)。

动态分配的方法如下:

struct video_device vdev = video_device_alloc();
if (vdev=NULL) {
    return -ENOMEN;
}
vdev->release = video_device_release;

上面的代码中,最后一行是必须的。也就是说,必须为video_device指定其release函数。该函数会在video_device的最后一个用户退出后被调用,作用就是释放所申请的空间。

如果video_device是被嵌入到其他结构体中的话,那么必须为其指定我们自己的释放函数。但是在这种静态分配的情况下,一般情况下,我们是不用释放什么空间的。此时就为其指定一个空函数,例如video_device_release_empty()。

2.1.2 初始化

video_device创建后,必定要初始化的成员如下:
  • v4l2_dev : 设置为指向v4l2_device
  • name : 设置一个唯一的名字
  • fops: 设置为指向我们自己的v4l2_file_operations结构体
  • ioctl_ops :设置为指向我们自己的v4l2_ioctl_ops结构体
  • lock :互斥量。若想要自己实现互斥操作,则可以不设置此变量。不过强烈建设让V4L2使用此变量确保互斥。
  • prio:优先级,用来实现VIDIOC_G/S_PRIORITY。若为NULL,则V4L2会使用v4l2_device的优先级。

2.1.2.1 互斥锁—lock成员

当lock不为NULL时,V4L2框架会使用此mutex确保操作的互斥性。

然而,对于某个比较耗时的操作,若在运行过程中加锁,会导致驱动无法为其他任务服务。此时,可以将lock设置为NULL,互斥操作由驱动自己负责,V4L2框架不参与。驱动编写者必须确保在调用V4L2的函数时,打开mutex,退出时关闭mutex。

2.1.2.2 V4L2优先级—prio成员

prio成员变量,指向v4l2_prio_state结构体。通过该结构体V4L2实现了文件优先级的功能。

先看下v4l2_prio_state的定义:

struct v4l2_prio_state {
    atomic_t prios[4];
};

V4L2有三个优先级,分别定义如下:

enum v4l2_priority {
    V4L2_PRIORITY_UNSET       = 0,  /* not initialized */
    V4L2_PRIORITY_BACKGROUND  = 1,
    V4L2_PRIORITY_INTERACTIVE = 2,
    V4L2_PRIORITY_RECORD      = 3,
    V4L2_PRIORITY_DEFAULT     = V4L2_PRIORITY_INTERACTIVE,
};

V4L2_PRIORITY_BACKGROUND:最低优先级,正如字面意思,主要用于后台处理,比如监视VBI数据的传输。 V4L2_PRIORITY_INTERACTIVE : 默认优先级。 V4L2_PRIORITY_RECORD :最高优先级。系统中只能有一个此优先级应用,此优先级的应用会阻塞其他所有的应用。

驱动中,是如何操作v4l2_prio_stat结构体中的4个atomic_t变量,从而实现优先级管理呢?概括之,如下:

  • 若这四个变量中的任何一个不为零,则表示该变量所对应的优先级有效。比如若prios[V4L2_PRIORITY_BACKGROUND]不为零,则程序处于V4L2_PRIORITY_BACKGROUND优先级。
  • 优先级的确定过程:从数组(atomic_t prios[4])末尾依次查询,返回第一个不为零的变量的位置。此位置即为程序的优先级。

V4L2中与优先级相关的函数有下列几个:

  • void v4l2_prio_init(struct v4l2_prio_state *global); : 清零所有的优先级变量。
  • int v4l2_prio_change(struct v4l2_prio_state *global, enum v4l2_priority *local, enum v4l2_priority new); : 设置指定的优先级(new)变量的值为1,并将以前的优先级(local)变量设置为0.
  • void v4l2_prio_open(struct v4l2_prio_state *global, enum v4l2_priority *local); : 设置程序的优先级为V4L2_PRIORITY_DEFAULT
  • void v4l2_prio_close(struct v4l2_prio_state *global, enum v4l2_priority local); : 设置当前优先级变量的值为0
  • enum v4l2_priority v4l2_prio_max(struct v4l2_prio_state *global); : 查询程序的优先级。查询方法如上面所示。
  • int v4l2_prio_check(struct v4l2_prio_state *global, enum v4l2_priority local); : 查询以确定程序是否能在指定的优先级下工作。如可以,则返回0;不可以则返回-EBUSY。
在驱动编写过程中,要注意对优先级的处理。对于ioctl类的函数,读取操作一般不需要调用v4l2_prio_check检查优先级;但是写操作(更改设备状态的操作)则必须检查优先级。

2.1.2.3 移花接木—ioctl_ops和fops成员

video_device的ioctl_ops和fops成员的任务就是实现将用户的操作(函数调用)”移花接木”般得映射到实际的驱动代码中。V4L2是如何实现映射的呢?首先使用fops将用户的ioctl操作映射到V4L2自己的__video_do_ioctl函数中,然后再通过ioctl_ops将具体的cmd传递给驱动中的相关函数。

下面以VIDIOC_QUERYCAP为例讲解。

static const struct file_operations v4l2_fops = {
.owner = THIS_MODULE,
.read = v4l2_read,
.write = v4l2_write,
.open = v4l2_open,
.get_unmapped_area = v4l2_get_unmapped_area,
.mmap = v4l2_mmap,
.unlocked_ioctl = v4l2_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = v4l2_compat_ioctl32,
#endif
.release = v4l2_release,
.poll = v4l2_poll,
.llseek = no_llseek,
};

vdev->cdev->ops = &v4l2_fops;
vdev->cdev->owner = owner;
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
if (ret < 0) {
    printk(KERN_ERR "%s: cdev_add failed\n", __func__);
    kfree(vdev->cdev);
    vdev->cdev = NULL;
    goto cleanup;
}

从上面的代码可以看出来,v4l2在注册字符设备时,将该字符设备的file_operations赋值为v4l2_fops。下面看看v4l2_ioctl的实现。

static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct video_device *vdev = video_devdata(filp);
int ret = -ENODEV;

if (vdev->fops->unlocked_ioctl) {
    if (vdev->lock && mutex_lock_interruptible(vdev->lock))
        return -ERESTARTSYS;
    if (video_is_registered(vdev))
        ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
    if (vdev->lock)
        mutex_unlock(vdev->lock);
} else if (vdev->fops->ioctl) {
    static DEFINE_MUTEX(v4l2_ioctl_mutex);
    struct mutex *m = vdev->v4l2_dev ?
        &vdev->v4l2_dev->ioctl_lock : &v4l2_ioctl_mutex;

    if (cmd != VIDIOC_DQBUF && mutex_lock_interruptible(m))
        return -ERESTARTSYS;
    if (video_is_registered(vdev))
        ret = vdev->fops->ioctl(filp, cmd, arg);
    if (cmd != VIDIOC_DQBUF)
        mutex_unlock(m);
} else
    ret = -ENOTTY;

return ret;
}

可以看到v4l2_ioctl的处理分为两种情况:unlocked_ioctl和正常的ioctl(具体区别后面在仔细讲)。不论哪种情况,都会调用vdev->fops->ioctl函数。看一下soc_camera驱动中此函数是如何实现的:

static struct v4l2_file_operations soc_camera_fops = {
.owner              = THIS_MODULE,
.open               = soc_camera_open,
.release    = soc_camera_close,
.unlocked_ioctl     = video_ioctl2,
.read               = soc_camera_read,
.mmap               = soc_camera_mmap,
.poll               = soc_camera_poll,
};

一般情况下,unlocked_ioctl都是指向video_ioctl2函数的。那么我们接下来看看video_ioctl2的代码:

long video_ioctl2(struct file *file,
    unsigned int cmd, unsigned long arg)
{
    return video_usercopy(file, cmd, arg, __video_do_ioctl);
}

video_ioctl2函数接着调用video_usercopy函数。video_usercopy函数会对用户传递过来的参数检查,将用户空间的数据拷贝到内核空间,调用我们指定的__video_do_ioctl函数,并将内核空间的数据拷贝到用户空间。

接下来终于到分析关键函数了,__video_do_ioctl :

static long __video_do_ioctl(struct file *file,
    unsigned int cmd, void *arg)
{
    struct video_device *vfd = video_devdata(file);
    const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
    void *fh = file->private_data;
    struct v4l2_fh *vfh = NULL;
    int use_fh_prio = 0;
    long ret_prio = 0;
    long ret = -ENOTTY;

    if (ops == NULL) {
        printk(KERN_WARNING "videodev: \"%s\" has no ioctl_ops.\n",
                vfd->name);
        return ret;
    }

    if ((vfd->debug & V4L2_DEBUG_IOCTL) &&
                !(vfd->debug & V4L2_DEBUG_IOCTL_ARG)) {
        v4l_print_ioctl(vfd->name, cmd);
        printk(KERN_CONT "\n");
    }

    if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags)) {
        vfh = file->private_data;
        use_fh_prio = test_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
    }

    if (use_fh_prio)
        ret_prio = v4l2_prio_check(vfd->prio, vfh->prio);

    switch (cmd) {

    /* --- capabilities ------------------------------------------ */
    case VIDIOC_QUERYCAP:
    {
        struct v4l2_capability *cap = (struct v4l2_capability *)arg;

        if (!ops->vidioc_querycap)
            break;

        cap->version = LINUX_VERSION_CODE;
        ret = ops->vidioc_querycap(file, fh, cap);
        if (!ret)
            dbgarg(cmd, "driver=%s, card=%s, bus=%s, "
                    "version=0x%08x, "
                    "capabilities=0x%08x, "
                    "device_caps=0x%08x\n",
                    cap->driver, cap->card, cap->bus_info,
                    cap->version,
                    cap->capabilities,
                    cap->device_caps);
        break;
        }
    ......
    return ret;
}

由于代码太长,只节选了VIDIOC_QUERYCAP的代码。__video_do_ioctl做完参数检查后,就是判断cmd的值并作相应处理。基本上所有的case的操作都一样:判断ops结构体中所对应的函数是否有效,若有效则使用用户指定的参数调用,否则返回-ENOTTY.

2.1.3 注册

2.2 v4l2文件句柄

2.3 v4l2_device

2.4 v4l2子设备

2.5 v4l2 control handler机制

2.5 v4l2 event机制